package aaa

// import aaa.utils.HashUtil
// import jakarta.persistence.*
import com.fasterxml.jackson.databind.ObjectMapper

import jakarta.persistence.Entity
import jakarta.persistence.Column
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.GenerationType
import aaa.generated_pb.OneoffProcessInfo
import aaa.generated_pb.oneoffProcessInfo as pb_oneoffProcessInfo
import aaa.generated_pb.FileIORecord

import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.persistence.metamodel.SingularAttribute
import jakarta.persistence.metamodel.StaticMetamodel

import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

import java.util.Date

import kotlin.concurrent.Volatile
import org.springframework.data.jpa.domain.Specification
import aaa.generated_pb.BashHistoryEntry
import aaa.generated_pb.LoginInfo
import aaa.generated_pb.UserInfo
import jakarta.persistence.criteria.Path

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ArrayNode
import java.util.concurrent.ConcurrentHashMap

object Dt {
    class Page<T>(var voList: List<T> , var totalCount: Long, var totalPages: Int)
    fun new2Old(l: LocalDateTime): Date = Date.from(l.atZone(ZoneId.systemDefault()).toInstant())
    val dtf: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")


    class Error(var code: Int = 0, var msg: String = "Success"){
        companion object {
            var SUCCESS: Error = Error(0, "Success")
            var DUPLICATE_ENTRY: Error = Error(1001, "duplicate entry")
            var PDF_GENERATE_FAILED: Error = Error(1002, "pdf generate failed")
        }
    }

/*
 class AuditRule {
     id: number; name: string;
     description: string; from: string;
     to: string; startTime: Date;
     endTime: Date; repeat: string;
 };
 */
    class BussinessAuditRule(@Id var id: Long = 0,
                              var name: String = "",
                              var description: String = "",
                              var from: String = "",
                              var to: String = "",
                              var keyWord: String = "",
                              var startTime: Date? = Date(0),
                              var endTime: Date? = Date(0),
                              var repeat: String = "")

    // --------------------------------------------------
    // 🦜 : <2025-03-18 Tue>
    /*
     class BusinessRecord {id: number; from: string; to: string; type: string; cmd: string; time: Date; auditResult: string; auditReason: string;

     */
    @Entity
    class BussinessRecord(@Id var id: Long = 0,
                          var from: String = "",
                          var to: String = "",
                          var type: String = "",
                          var cmd: String = "",
                          var time: Date? = Date(0),
                          var auditResult: String = "",
                          var auditReason: String = "")

    @StaticMetamodel(BussinessRecord::class)
    internal object BussinessRecord_ {
        @Volatile var time: SingularAttribute<BussinessRecord?, Date>? = null
        @Volatile var cmd: SingularAttribute<BussinessRecord?, String>? = null
        @Volatile var from: SingularAttribute<BussinessRecord?, String>? = null
        @Volatile var to: SingularAttribute<BussinessRecord?, String>? = null
        @Volatile var type: SingularAttribute<BussinessRecord?, String>? = null
        @Volatile var auditResult: SingularAttribute<BussinessRecord?, String>? = null
        @Volatile var auditReason: SingularAttribute<BussinessRecord?, String>? = null
        fun timeBefore(time: Date): Specification<BussinessRecord?> {
            return Specification { root: Root<BussinessRecord?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(BussinessRecord_.time), time)
            }
        }

        fun timeAfter(time: Date): Specification<BussinessRecord?> {
            return Specification { root: Root<BussinessRecord?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(BussinessRecord_.time), time)
            }
        }

        fun cmdContains(cmd: String): Specification<BussinessRecord?> {
            return Specification { root: Root<BussinessRecord?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(BussinessRecord_.cmd), "%$cmd%")
            }
        }

        fun fromContains(from: String): Specification<BussinessRecord?> {
            return Specification { root: Root<BussinessRecord?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(BussinessRecord_.from), "%$from%")
            }
        }

        fun toContains(to: String): Specification<BussinessRecord?> {
            return Specification { root: Root<BussinessRecord?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(BussinessRecord_.to), "%$to%")
            }
        }
    }


    // --------------------------------------------------

    @Entity(name = "OneoffProcessInfo2")
    class OneoffProcessInfo2(
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0,
        var time: Date? = null,
        var cmd: String = "",
        var pid: Int = -1,
        var ppid: Int = -1,
        var uid: Int = -1,
        @Column(length = 512) var argv: String = "",  // this is capped to 128 at the agent side. (the C code)
        var retVal: Int = 0,
        var nFolded: Int = 0,
        var hash: String = "",   //this is non-empty if the record is registered on the chain
        var hashUri: String = "", //this is the uri to the hashed file 
        var registerTime: Date? = null,

        var spsScore: Float = 0.0f, // this is the score of the process being suspicious: 0 ~ 1.0
        var spsReason: String = "" // this is the reason why the process is suspicious
    ){
        companion object {
            fun convertFromPb(i: OneoffProcessInfo): OneoffProcessInfo2 {
                var o =  OneoffProcessInfo2(
                    time = Date(i.unixMilli),
                    //🦜 : the `long` ctor for `Date` accepts ms
                    cmd = i.comm,
                    pid = i.pid,
                    ppid = i.ppid,
                    uid = i.uid,
                    argv = i.argv,
                    retVal = i.retVal,
                )
                return o
            }

            fun toAuditJsonArray(l: List<OneoffProcessInfo2>, m: Map<Int, UserInfo2>): ArrayNode{
                val json = ObjectMapper()
                val a = json.createArrayNode()
                for (i in l){
                    a.add(i.toAuditJson(m))
                }
                return a
            }
        }

        // fun toAuditJson(m: ConcurrentHashMap<Int, UserInfo2>): JsonNode {
        fun toAuditJson(m: Map<Int, UserInfo2>): JsonNode {
            val json = ObjectMapper()
            val o = json.createObjectNode()
            o.put("cmd", cmd)

            val v = argv.split(" ")
            val va = json.createArrayNode()
            if (v.size > 1){    //store v[1:]
                for (i in 1..<v.size){
                    va.add(v[i])
                }
            }
            o.set<ArrayNode>("argv", va)

            // user
            val u = m.get(uid)
            o.put("user", u?.name ?: "unknown")
            return o
        }

        fun toPb(): OneoffProcessInfo {
            return pb_oneoffProcessInfo{
                unixMilli = time?.time ?: 0
                comm = cmd
                pid = pid
                ppid = ppid
                uid = uid
                argv = argv
                retVal = retVal
                nFolded = nFolded
            }
        }
    }

    @StaticMetamodel(OneoffProcessInfo2::class)
    internal object OneoffProcessInfo2_ {
        @Volatile var time: SingularAttribute<OneoffProcessInfo2?, Date>? = null
        @Volatile var cmd: SingularAttribute<OneoffProcessInfo2?, String>? = null
        // @Volatile var pid: SingularAttribute<OneoffProcessInfo2?, Int>? = null
        // @Volatile var ppid: SingularAttribute<OneoffProcessInfo2?, Int>? = null
        @Volatile var uid: SingularAttribute<OneoffProcessInfo2?, Int>? = null
        @Volatile var argv: SingularAttribute<OneoffProcessInfo2?, String>? = null
        // @Volatile var retVal: SingularAttribute<OneoffProcessInfo2?, Int>? = null
        @Volatile var hash: SingularAttribute<OneoffProcessInfo2?, String>? = null
        @Volatile var spsScore: SingularAttribute<OneoffProcessInfo2?, Float>? = null
        @Volatile var spsReason: SingularAttribute<OneoffProcessInfo2?, String>? = null

        fun timeBefore(time: Date): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(OneoffProcessInfo2_.time), time)
            }
        }

        fun timeAfter(time: Date): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(OneoffProcessInfo2_.time), time)
            }
        }

        fun cmdContains(cmd: String): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(OneoffProcessInfo2_.cmd), "%$cmd%")
            }
        }

        fun uidEquals(uid: Int): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.equal(root.get(OneoffProcessInfo2_.uid), uid)
            }
        }

        fun hashContains(hash: String): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(OneoffProcessInfo2_.hash), "%$hash%")
            }
        }

        fun spsScoreGreaterThan(spsScore: Float): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(OneoffProcessInfo2_.spsScore), spsScore)
            }
        }

        fun spsScoreLessThan(spsScore: Float): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(OneoffProcessInfo2_.spsScore), spsScore)
            }
        }

        fun spsReasonContains(spsReason: String): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(OneoffProcessInfo2_.spsReason), "%$spsReason%")
            }
        }

        fun spsReasonIsEmpty(): Specification<OneoffProcessInfo2?> {
            return Specification { root: Root<OneoffProcessInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.equal(root.get(OneoffProcessInfo2_.spsReason), "")
            }
        }
    }

    class UserInfo2(
        var uid: String = "",
        var name: String = "",
        var homeDir: String = "",
        var description: String = "",
        var shell: String = "",
    ){

        val uidInt : Int
            get() {
                try {
                    return uid.toInt()
                } catch (e: NumberFormatException) {
                    return -1
                }
            }

        companion object {
            fun convertFromPb(i: UserInfo): UserInfo2 {
                val i2 = UserInfo2()
                i2.uid = i.uid
                i2.name = i.name
                i2.homeDir = i.homeDir
                i2.description = i.description
                i2.shell = i.shell
                return i2
            }
        }
    }                           //class UserInfo2

    @Entity
    class LoginInfo2(
        @Id @GeneratedValue var id: Long = 0,
        var user: String = "",
        var terminal: String = "",
        var loginIp: String = "",
        var loginTime: Date? = null,
        var logoutTime: String = "", // 登出时间 【特殊值释义 crash：系统崩溃或意外关机；down:用户图形界面登出】
    ) {
        companion object {
            /*
             🦜 : Why `loginTime` is stored as Date, but not `logoutTime`?

             🐢 : Because `logoutTime` is not always a valid date, it can be
             special values such as `crash` or `down`.
             */
            fun convertFromPb(i: LoginInfo): LoginInfo2 {
                val i2 = LoginInfo2()
                i2.user = i.user
                i2.terminal = i.terminal
                i2.loginIp = i.loginIp
                i2.loginTime = new2Old(LocalDateTime.parse(i.loginTime, dtf)) // -> the old java.util.Date
                i2.logoutTime = i.logoutTime // this can be "crash" or "down"
                return i2
            }
        }
    }                           //class LoginInfo2

    @StaticMetamodel(LoginInfo2::class)
    internal object LoginInfo2_ {
        @Volatile
        var user: SingularAttribute<LoginInfo2?, String>? = null

        @Volatile
        var loginTime: SingularAttribute<LoginInfo2?, Date>? = null

        fun userContains(user: String): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(LoginInfo2_.user), "%$user%")
            }
        }

        fun loginTimeAfter(loginTime: Date): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _ : CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(LoginInfo2_.loginTime), loginTime)
            }
        }

        fun loginTimeBefore(loginTime: Date): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _ : CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(LoginInfo2_.loginTime), loginTime)
            }
        }
    }                           //class LoginInfo2_


    /*
     message FileIORecord {
     int64 unixMilli = 1;
     string eventType = 2; // read, write, open, close, ...
     string fileName = 3;
     string user = 4;
     }
     */
    @Entity
    class FileIORecord2(
        @Id @GeneratedValue var id: Long = 0,
        var time: Date = Date(0),
        var eventType: String = "",
        var fileName: String = "",
        var fileTo: String = "",
        var user: String = "",
        var hash: String = "",
    ) {
        companion object {
            fun convertFromPb(i: FileIORecord): FileIORecord2 {
                val i2 = FileIORecord2()
                i2.time = Date(i.unixMilli)
                i2.eventType = i.eventType
                i2.fileName = i.fileName
                i2.user = i.user
                return i2
            }
        }
    }                           //class FileIORecord2

    @StaticMetamodel(FileIORecord2::class) // [🦜: AI generated]
    internal object FileIORecord2_ {
        @Volatile
        var time: SingularAttribute<FileIORecord2?, Date>? = null

        @Volatile
        var eventType: SingularAttribute<FileIORecord2?, String>? = null

        @Volatile
        var fileName: SingularAttribute<FileIORecord2?, String>? = null

        @Volatile
        var user: SingularAttribute<FileIORecord2?, String>? = null

        @Volatile
        var hash: SingularAttribute<FileIORecord2?, String>? = null

        fun timeAfter(time: Date): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(FileIORecord2_.time), time)
            }
        }

        fun timeBefore(time: Date): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(FileIORecord2_.time), time)
            }
        }

        fun eventTypeContains(eventType: String): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(FileIORecord2_.eventType), "%$eventType%")
            }
        }

        fun fileNameContains(fileName: String): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(FileIORecord2_.fileName), "%$fileName%")
            }
        }

        fun userContains(user: String): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(FileIORecord2_.user), "%$user%")
            }
        }
        fun hashContains(hash: String): Specification<FileIORecord2?> {
            return Specification { root: Root<FileIORecord2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(FileIORecord2_.hash), "%$hash%")
            }
        }
    }                           //class FileIORecord2_




}                               //object Dt
